﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.Common.Utilities;
using iFinity.DNN.Modules.UrlMaster;
using iFinity.DNN.Modules.CatalookUrlProvider.Entities;
using System.Web;
namespace iFinity.DNN.Modules.CatalookUrlProvider
{
    internal static class UrlController
    {
        //keys used for cache entries for Urls and Querystrings
        private const string FriendlyUrlIndexKey = "ifty_cat_Urls_Portal{0}";
        private const string QueryStringIndexKey = "ifty_cat_QueryString_Portal{0}";
        private const string TabCatIndexKey = "ifty_cat_TabCategory_Portal{0}";
        private const string CatTabIndexKey = "ifty_cat_CategoryTab_Portal{0}";
        /// <summary>s
        /// Checks for, and adds to the indexes, a missing item.
        /// </summary>
        /// <param name="itemId"></param>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="provider"></param>
        /// <param name="options"></param>
        /// <param name="messages"></param>
        /// <returns>Valid path if found</returns>
        internal static string CheckForMissingItemId(int itemId, string itemType, int portalId, string cultureCode, CatalookModuleProvider provider, FriendlyUrlOptions options, CatalookUrlOptions clOptions, Hashtable friendlyUrlIndex, ref List<string> messages)
        {
            string path = null;
            //looking for a new item that has been created and isn't in the index
            //first check to see if a variant for the base culture exists                           
            string primaryCultureKey = FriendlyUrlInfo.GetItemKey(itemId, itemType, clOptions.PrimaryCultureCode);
                            
            path = (string)friendlyUrlIndex[primaryCultureKey];
            if (path != null)
            {
                //exists for the default portal culture, but not for this culture
                path = Regex.Replace(path, @"[a-z][a-z]-[A-Z][A-Z]$", "");//remove prior culture code
                if (path.EndsWith("-") == false)
                    path += "-";
                path += cultureCode;//add on the specified culture code 
                string thisCultureKey = FriendlyUrlInfo.GetItemKey(itemId, itemType, cultureCode);
                //friendly url index - look up by entryid, find Url
                //check to see if friendly url matches any page paths - even though it shouldn't, right?
                if (friendlyUrlIndex.ContainsKey(thisCultureKey) == false)//shouldn't return duplicate because content is controlled by module logic
                    friendlyUrlIndex.Add(thisCultureKey, path);

                Hashtable queryStringIndex = GetQueryStringIndex(portalId, provider, options, clOptions, false);
                //restash the indexes
                StoreIndexes(friendlyUrlIndex, GetFriendlyUrlIndexKeyForPortal(portalId), queryStringIndex, GetQueryStringIndexCacheKeyForPortal(portalId));
            }
            else
            {
                //find the item, and if found, add that to the list
                string msgPart = string.Format("Item (id:{0}, type:{1}, portal:{2})", itemId, itemType, portalId);
                FriendlyUrlInfo friendlyUrl = Data.DataProvider.Instance().GetItemUrl(itemId, itemType, portalId, clOptions);
                messages.Add(msgPart + " not found :  Checking Item directly");
                if (friendlyUrl != null)
                {
                    messages.Add(msgPart + " found : Adding item to index");
                    Dictionary<string, string> categoryParents = new Dictionary<string, string>();
                    Hashtable queryStringIndex = GetQueryStringIndex(portalId, provider, options, clOptions, false);
                    AddFriendlyUrlToIndexes(portalId, friendlyUrl, provider, clOptions, options, ref friendlyUrlIndex, ref queryStringIndex, ref categoryParents);
                    //also make the friendly url to return
                    path = MakeItemFriendlyUrl(friendlyUrl, provider, options, clOptions);
                    //restash the indexes
                    StoreIndexes(friendlyUrlIndex, GetFriendlyUrlIndexKeyForPortal(portalId), queryStringIndex, GetQueryStringIndexCacheKeyForPortal(portalId));
                }
            }
            return path;
        }
        /// <summary>
        /// Construct a Url with the friendly Url path
        /// </summary>
        /// <param name="httpAlias"></param>
        /// <param name="tabId"></param>
        /// <param name="friendlyUrl"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static string BuildFullyQualifiedFriendlyUrl(string httpAlias, int tabId, string[] urlParms, CatalookModuleProvider provider, string cultureCode)
        {
            DotNetNuke.Entities.Portals.PortalAliasInfo alias = DotNetNuke.Entities.Portals.PortalAliasController.GetPortalAliasInfo(httpAlias);
            DotNetNuke.Entities.Portals.PortalSettings ps = new DotNetNuke.Entities.Portals.PortalSettings(tabId, alias);
            string baseUrl = DotNetNuke.Common.Globals.NavigateURL(tabId, false, ps, "", cultureCode, urlParms); //add on with page path
            return baseUrl;
        }
        internal static string BuildFullyQualifiedFriendlyUrl(string httpAlias, int tabId, string friendlyUrl, CatalookModuleProvider provider)
        {
            DotNetNuke.Entities.Portals.PortalAliasInfo alias = DotNetNuke.Entities.Portals.PortalAliasController.GetPortalAliasInfo(httpAlias);
            DotNetNuke.Entities.Portals.PortalSettings ps = new DotNetNuke.Entities.Portals.PortalSettings(tabId, alias);
            friendlyUrl = provider.EnsureNotLeadingChar("/", friendlyUrl);//remove leading path
            string baseUrl = DotNetNuke.Common.Globals.NavigateURL(tabId, ps, "", friendlyUrl); //add on with page path
            return baseUrl;
        }


        /// <summary>
        /// Creates a Friendly Url For the Item
        /// </summary>
        /// <param name="friendlyUrl">Object containing the relevant properties to create a friendly url from</param>
        /// <param name="provider">The active module provider</param>
        /// <param name="options">THe current friendly Url Options</param>
        /// <returns></returns>
        private static string MakeItemFriendlyUrl(FriendlyUrlInfo friendlyUrl, CatalookModuleProvider provider, FriendlyUrlOptions options, CatalookUrlOptions clOptions)
        {
            //calls back up the module provider to utilise the CleanNameForUrl method, which creates a safe Url using the current Url Master options.
            string friendlyUrlPath = friendlyUrl.urlFragment;
            if (clOptions.ProductIdInUrl == true && friendlyUrl.itemType.ToLower() == "product")
            {
                //add on a culture + productId 
                //like enUS12543
                string urlCulture = friendlyUrl.cultureCode;
                if (urlCulture != null)
                    urlCulture = urlCulture.Replace("-","");
                friendlyUrlPath += " " + urlCulture + friendlyUrl.itemId.ToString();
            }
            friendlyUrlPath =  provider.CleanNameForUrl(friendlyUrlPath, options);
            switch (friendlyUrl.itemType.ToLower())
            {
                case "product":
                    //product can be derived from html (description).  Therefore, clean and truncate length
                    friendlyUrlPath = friendlyUrlPath.Replace("\r\n", "");
                    if (friendlyUrlPath.Length > 149)
                        friendlyUrlPath = friendlyUrlPath.Substring(0, 150);
                    break;

                case "category":
                    //no difference - category style chosen at index build stage
                    break;
                case "advcategory":
                    //no difference - category style chosen at index build stage
                    break;
            }
            return friendlyUrlPath;
        }
        #region index retrieval code
        /// <summary>
        /// Returns a friendly url index from the cache or database.
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="CatalookModuleProvider"></param>
        /// <param name="urlOptions"></param>
        /// <returns></returns>
        internal static Hashtable GetFriendlyUrlIndex(int portalId, CatalookModuleProvider provider, FriendlyUrlOptions urlOptions, CatalookUrlOptions clOptions)
        {
            string furlCacheKey = GetFriendlyUrlIndexKeyForPortal(portalId);
            Hashtable friendlyUrlIndex = DataCache.GetCache<Hashtable>(furlCacheKey);
            if (friendlyUrlIndex == null)
            {
                string qsCacheKey = GetQueryStringIndexCacheKeyForPortal(portalId);
                Hashtable queryStringIndex = null;
                //build index for tab
                BuildUrlIndexes(portalId, provider, urlOptions, clOptions, out friendlyUrlIndex, out queryStringIndex);
                StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
            }
            return friendlyUrlIndex;

        }
        /// <summary>
        /// Return the index of all the querystrings that belong to friendly urls for the specific tab.
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static Hashtable GetQueryStringIndex(int portalId, CatalookModuleProvider provider, FriendlyUrlOptions options, CatalookUrlOptions clOptions, bool forceRebuild)
        {
            string qsCacheKey = GetQueryStringIndexCacheKeyForPortal(portalId);
            Hashtable queryStringIndex = DataCache.GetCache<Hashtable>(qsCacheKey);
            if (queryStringIndex == null || forceRebuild)
            {
                string furlCacheKey = GetFriendlyUrlIndexKeyForPortal(portalId);
                Hashtable friendlyUrlIndex = null;
                //build index for tab
                BuildUrlIndexes(portalId, provider, options, clOptions, out friendlyUrlIndex, out queryStringIndex);
                StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
            }
            return queryStringIndex;
        }
        /// <summary>
        /// Returns an index which is keyed by tab, and lists the category associated with the tab
        /// </summary>
        /// <param name="portalId"></param>
        /// <param name="provider"></param>
        /// <param name="clOptions"></param>
        /// <param name="forceRebuild"></param>
        /// <returns></returns>
        internal static TabCategoryTable GetTabCategoryIndex(int portalId,  CatalookModuleProvider provider, CatalookUrlOptions clOptions, bool forceRebuild)
        {
            string tcCacheKey = GetTabCategoryCacheKeyForPortal(portalId);
            TabCategoryTable tabCategoryIndex = DataCache.GetCache<TabCategoryTable>(tcCacheKey);
            if (tabCategoryIndex == null || forceRebuild)
            {
                string ctCacheKey = GetCategoryTabCacheKeyForPortal(portalId);
                CategoryTabTable categoryTabIndex = null;
                BuildTabCategoryIndexes(portalId, provider, clOptions, out tabCategoryIndex, out categoryTabIndex);
                StoreTabCategoryIndexes(tabCategoryIndex, tcCacheKey, categoryTabIndex, ctCacheKey);
            }
            return tabCategoryIndex;
        }
        internal static CategoryTabTable GetCategoryTabIndex(int portalId, CatalookModuleProvider provider, CatalookUrlOptions clOptions, bool forceRebuild)
        {
            string ctCacheKey = GetCategoryTabCacheKeyForPortal(portalId);
            CategoryTabTable categoryTabIndex = null;
            if (categoryTabIndex == null || forceRebuild)
            {
                string tcCacheKey = GetTabCategoryCacheKeyForPortal(portalId);
                TabCategoryTable tabCategoryIndex = DataCache.GetCache<TabCategoryTable>(tcCacheKey);
                BuildTabCategoryIndexes(portalId, provider, clOptions, out tabCategoryIndex, out categoryTabIndex);
                StoreTabCategoryIndexes(tabCategoryIndex, tcCacheKey, categoryTabIndex, ctCacheKey);
            }
            return categoryTabIndex;
        }
        #endregion




        private static void AddUniqueUrlToQueryStringIndex(string furlKey, ref string qsKey, string qsValue, int portalId, string cultureCode, Hashtable queryStringIndex, FriendlyUrlOptions options, bool addSuffixIfDuplicateFound, out string suffix)
        {
            DotNetNuke.Entities.Tabs.TabController tc = new DotNetNuke.Entities.Tabs.TabController();
            bool duplicate = false;
            suffix = "";//can hold a de-duplicating suffix
            int suffixCounter = 1;
            bool furlKeyUsed = false;
            bool cultureCodeKeyUsed = false;
            do
            {
                duplicate = false;//always start in the assumption that this is not a duplicate
                DotNetNuke.Entities.Tabs.TabInfo matchingTab = tc.GetTabByName(qsKey, portalId);
                if (matchingTab != null)
                    duplicate = true;
                else
                    if (portalId >= 0)
                    {
                        matchingTab = tc.GetTabByName(qsKey, -1);//host tabs
                        if (matchingTab != null)
                            duplicate = true;
                    }

                if (duplicate == false)
                {
                    //try and add to index
                    if (queryStringIndex.ContainsKey(qsKey) == false)
                        queryStringIndex.Add(qsKey, qsValue);
                    else
                        duplicate = true;
                }
                if (duplicate == true)
                {
                    if (cultureCodeKeyUsed == false)
                    {
                        cultureCodeKeyUsed = true;
                        suffix = options.PunctuationReplacement + cultureCode;
                        qsKey += suffix.ToLower();
                    }
                    else
                    {
                        if (furlKeyUsed == false)
                        {
                            furlKeyUsed = true;
                            suffix = options.PunctuationReplacement + furlKey;
                            qsKey += suffix.ToLower();
                        }
                        else
                        {
                            suffix += suffixCounter.ToString();
                            qsKey += suffix.ToLower();
                        }
                    }
                }
            }
            while (duplicate == true && addSuffixIfDuplicateFound == true);
        }
        private static string GetParentPath(string childKey, string parentKey, string childPath, ref Dictionary<string, string> categoryParents, ref Hashtable friendlyUrlIndex)
        {
            //now lookup on that parent
            string parentPath = null;
            if (friendlyUrlIndex.ContainsKey(parentKey) && friendlyUrlIndex.ContainsKey(childKey))
            {
                parentPath = (string)friendlyUrlIndex[parentKey];
                //update base path by appending values together
                if (categoryParents.ContainsKey(parentKey))
                {
                    string grandParentKey = categoryParents[parentKey];
                    string grandParentPath = GetParentPath(parentKey, grandParentKey, parentPath, ref categoryParents, ref friendlyUrlIndex);
                    if (grandParentPath != null)
                        parentPath = grandParentPath + "/" + parentPath;
                }
            }
            return parentPath;
        }
 
        private static void BuildUrlIndexes(int portalId, CatalookModuleProvider provider, FriendlyUrlOptions options, CatalookUrlOptions clOptions, out Hashtable friendlyUrlIndex, out Hashtable queryStringIndex)
        {
            friendlyUrlIndex = new Hashtable();
            queryStringIndex = new Hashtable();
            //call database procedure to get list of items
            FriendlyUrlInfoCol itemUrls = null;
            Data.DataProvider.Instance().GetCatalookItemsForPortal(portalId, clOptions, out itemUrls);
            Dictionary<string, string> categoryParents = new Dictionary<string, string>();
            if (itemUrls != null)
            {
                //build up the dictionary
                foreach (FriendlyUrlInfo itemUrl in itemUrls)
                {
                    AddFriendlyUrlToIndexes(portalId, itemUrl,provider, clOptions, options, ref friendlyUrlIndex, ref queryStringIndex, ref categoryParents);
                }
                //go back and recursively check for category parents to be updated
                if (categoryParents != null && categoryParents.Count > 0)
                {
                    Dictionary<string, string> updates = new Dictionary<string, string>();
                    //reallocate the friendly urls recursively so that categories include their parent path
                    foreach (string furlKey in categoryParents.Keys)
                    {
                        //got the key for the friendly url
                        //now find the parent
                        string parentKey = categoryParents[furlKey];
                        string childPath = (string)friendlyUrlIndex[furlKey];
                        string parentPath = GetParentPath(furlKey, parentKey, childPath, ref categoryParents, ref friendlyUrlIndex);
                        if (parentPath != null)
                            childPath = parentPath + "/" + childPath;
                        updates.Add(furlKey, childPath);//don't update until all done
                    }
                    //now process the update list and update any values that had hierarchial categories
                    foreach (string key in updates.Keys)
                    {
                        string oldVal = (string)friendlyUrlIndex[key];
                        string qsKey = oldVal.ToLower();
                        if (queryStringIndex.ContainsKey(qsKey))
                        {
                            //update the querystring index
                            string qsVal = (string)queryStringIndex[qsKey];
                            queryStringIndex.Remove(qsKey);
                            queryStringIndex.Add(updates[key].ToLower(), qsVal);
                        }
                        //update the new friendly url index
                        friendlyUrlIndex[key] = updates[key];
                    }
                }
            }
        }

        private static void AddFriendlyUrlToIndexes(int portalId, FriendlyUrlInfo itemUrl, CatalookModuleProvider provider, CatalookUrlOptions clOptions
                                                  , FriendlyUrlOptions options, ref Hashtable friendlyUrlIndex, ref Hashtable queryStringIndex, ref Dictionary<string,string> categoryParents)
        {
            string furlKey = itemUrl.FUrlKey;

            //querystring index - look up by url, find querystring for the item
            string furlValue = MakeItemFriendlyUrl(itemUrl, provider, options, clOptions);
            string qsKey = furlValue.ToLower();//the querystring lookup is the friendly Url value - but converted to lower case

            string qsValue = null;
            //string itemId = itemUrl.itemId.ToString();
            //string parentId = itemUrl.parentId.ToString();
            qsValue = BuildQsValue(portalId, provider, clOptions, itemUrl, ref categoryParents);

            string suffix = "";
            AddUniqueUrlToQueryStringIndex(furlKey, ref qsKey, qsValue, portalId, itemUrl.cultureCode, queryStringIndex, options, true, out suffix);

            //if the suffix for the qsKey was changed, we need to add it to the friendly url used for the friendly url index
            furlValue += suffix;

            //friendly url index - look up by entryid, find Url
            //check to see if friendly url matches any page paths
            if (friendlyUrlIndex.ContainsKey(furlKey) == false)//shouldn't return duplicate because content is controlled by module logic
                friendlyUrlIndex.Add(furlKey, furlValue);

            //if the options aren't standard, also add in some other versions that will identify the right entry but will get redirected
            if (options.PunctuationReplacement != "")
            {
                FriendlyUrlOptions altOptions = options.Clone();
                altOptions.PunctuationReplacement = "";//how the urls look with no replacement
                string altQsKey = MakeItemFriendlyUrl(itemUrl, provider, altOptions, clOptions).ToLower();//keys are always lowercase
                string altQsValue = qsValue + "&do301=true&&rr=Title_Space_Replacement";
                AddUniqueUrlToQueryStringIndex(furlKey, ref altQsKey, altQsValue, portalId, itemUrl.cultureCode, queryStringIndex, options, false, out suffix);
            }            
        }
        /// <summary>
        /// Builds a Querystring to match the supplied Url
        /// </summary>
        /// <param name="portalId"></param>
        /// <param name="clOptions"></param>
        /// <param name="itemUrl"></param>
        /// <returns></returns>
        private static string BuildQsValue(int portalId, CatalookModuleProvider provider, CatalookUrlOptions clOptions, FriendlyUrlInfo itemUrl, ref Dictionary<string,string> categoryParents)
        {
            string qsValue = "";
            string itemId = itemUrl.itemId.ToString();
            string parentId = itemUrl.parentId.ToString();
            switch (itemUrl.itemType.ToLower())
            {
                case "product":
                    qsValue = "&productID=" + itemId;//the querystring is just the entryId parameter
                    break;
                case "advcategory":
                    qsValue = "&CategoryID=" + itemId + "&Level=a";//level=a -> advanced category
                    if (parentId != "-1")
                    {
                        //this category has a parent
                        categoryParents.Add(itemUrl.FUrlKey, FriendlyUrlInfo.GetItemKey(itemUrl.parentId, itemUrl.itemType, itemUrl.cultureCode));
                    }
                    break;
                case "category":
                    //ordinary category listing - 1 level
                    qsValue = "&CategoryID=" + itemId + "&Level=1";
                    break;
            }


            //when not including the dnn page path into the friendly Url, then include the tabid in the querystring
            if (provider.AlwaysUsesDnnPagePath(portalId) == false)
                qsValue = "?TabId=" + clOptions.TabId.ToString() + qsValue;

            if (string.IsNullOrEmpty(itemUrl.cultureCode) == false)
                //add on the culture code
                qsValue += "&language=" + itemUrl.cultureCode;

            return qsValue;
        }
        private static void BuildTabCategoryIndexes(int portalId, CatalookModuleProvider provider, CatalookUrlOptions clOptions, out TabCategoryTable tabCategoryIndex, out CategoryTabTable categoryTabIndex)
        {
            tabCategoryIndex = new TabCategoryTable();
            categoryTabIndex = new CategoryTabTable();
            List<TabCategory> tabCategories = null;
            Data.DataProvider.Instance().GetCategoriesForTabs(portalId, clOptions, out tabCategories);
            if (tabCategories != null)
            {
                foreach (TabCategory tabCat in tabCategories)
                {
                    int tabid = tabCat.TabId;
                    int catId = tabCat.CategoryId;

                    if (tabCategoryIndex.ContainsKey(tabid) == false)
                        tabCategoryIndex.Add(tabid, catId);
                    if (categoryTabIndex.Contains(catId) == false)
                        categoryTabIndex.Add(catId, tabid);
                }
            }
            
        }
        /// <summary>
        /// REbuilds the two indexes and re-stores them into the cache
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="provider"></param>
        /// <param name="options"></param>
        private static void RebuildIndexes(int portalId, CatalookModuleProvider provider, FriendlyUrlOptions options, CatalookUrlOptions clOptions)
        {
            Hashtable queryStringIndex = null;
            Hashtable friendlyUrlIndex = null;
            string qsCacheKey = GetQueryStringIndexCacheKeyForPortal(portalId);
            string furlCacheKey = GetFriendlyUrlIndexKeyForPortal(portalId);
            //build index for tab
            BuildUrlIndexes(portalId, provider, options, clOptions, out friendlyUrlIndex, out queryStringIndex);
            StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
        }

        #region index cache storage
        /// <summary>
        /// Store the two indexes into the cache
        /// </summary>
        /// <param name="friendlyUrlIndex"></param>
        /// <param name="friendlyUrlCacheKey"></param>
        /// <param name="queryStringIndex"></param>
        /// <param name="queryStringCacheKey"></param>
        private static void StoreIndexes(Hashtable friendlyUrlIndex, string friendlyUrlCacheKey, Hashtable queryStringIndex, string queryStringCacheKey)
        {
            TimeSpan expire = new TimeSpan(24, 0, 0);
            DataCache.SetCache(friendlyUrlCacheKey, friendlyUrlIndex, expire);
            DataCache.SetCache(queryStringCacheKey, queryStringIndex, expire);
        }
        private static void StoreTabCategoryIndexes(TabCategoryTable tabCategoryIndex, string tcCacheKey, CategoryTabTable categoryTabIndex, string ctCacheKey)
        {
            TimeSpan expire = new TimeSpan(24, 0, 0);
            DataCache.SetCache(tcCacheKey, tabCategoryIndex, expire);
            DataCache.SetCache(ctCacheKey, categoryTabIndex, expire);
        }
        #endregion
        #region cache key generation
        /// <summary>
        /// Return the caceh key for a tab index
        /// </summary>
        /// <param name="portalId"></param>
        /// <returns></returns>
        private static string GetFriendlyUrlIndexKeyForPortal(int portalId)
        {
            return string.Format(FriendlyUrlIndexKey, portalId);
        }
        private static string GetQueryStringIndexCacheKeyForPortal(int portalId)
        {
            return string.Format(QueryStringIndexKey, portalId);
        }

        private static string GetCategoryTabCacheKeyForPortal(int portalId)
        {
            return string.Format(TabCatIndexKey, portalId);
        }

        private static string GetTabCategoryCacheKeyForPortal(int portalId)
        {
            return string.Format(CatTabIndexKey, portalId);
        }
        #endregion
        #region url generation routines

        internal static bool MakeProductUrl(CatalookModuleProvider provider, Match productUrlMatch, Regex productUrlRegex, string friendlyUrlPath, TabInfo tab, FriendlyUrlOptions options, CatalookUrlOptions clOptions, string cultureCode, ref string endingPageName, ref bool useDnnPagePath, ref List<string> messages, out string productUrl)
        {
            bool result = false;
            productUrl = null;
            //this is a url that looks like an article url.  We want to modify it and create the new one.
            string rawId = productUrlMatch.Groups["productId"].Value;
            int productId = 0;
            if (int.TryParse(rawId, out productId) && (clOptions.StartingProductId <= productId))
            {
                Hashtable friendlyUrlIndex = null; //the friendly url index is the lookup we use
                //we have obtained the item Id out of the Url
                //get the friendlyUrlIndex (it comes from the database via the cache)
                friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(tab.PortalID, provider, options, clOptions);
                if (friendlyUrlIndex != null)
                {
                    string furlkey = null; 
                    //item urls are indexed with p + productID ("p5")  + the culture code
                    furlkey = "p" + productId.ToString() + "-" + cultureCode;  //create the lookup key for the friendly url index

                    string path = (string)friendlyUrlIndex[furlkey];//check if in the index
                    if (path == null)
                    {
                        //could be a new item that has been created and isn't in the index
                        //do a direct call and find out if it's there
                        path = UrlController.CheckForMissingItemId(productId, "product", tab.PortalID, cultureCode, provider, options, clOptions, friendlyUrlIndex, ref messages);
                    }
                    if (path != null) //got a valid path
                    {
                        //url found in the index for this entry.  So replace the matched part of the path with the friendly url
                        if (productUrlMatch.Groups["l"].Success) //if the path had a leading /, then make sure to add that onto the replacement
                            path = provider.EnsureLeadingChar("/", path);

                        /* finish it all off */
                        messages.Add("Item Friendly Url Replacing : " + friendlyUrlPath + " in Path : " + path);

                        //this is the point where the Url is modified!
                        //replace the path in the path - which leaves any other parts of a path intact.
                        productUrl = productUrlRegex.Replace(friendlyUrlPath, path);//replace the part in the friendly Url path with it's replacement.

                        //check if this tab is the one specified to not use a path
                        if (provider.NoDnnPagePathTabId == tab.TabID)
                            useDnnPagePath = false;//make this Url relative from the site root

                        //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                        endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;

                        result = true;
                    }
                }
            }
            return result;
        }

        internal static bool MakeCategoryUrl(CatalookModuleProvider provider, MatchCollection categoryUrlMatches, Regex categoryUrlRegex, string friendlyUrlPath, TabInfo tab, FriendlyUrlOptions options, CatalookUrlOptions clOptions, string cultureCode, ref string endingPageName, ref bool useDnnPagePath, ref List<string> messages, out string categoryUrl)
        {
            bool result = false;
            categoryUrl = friendlyUrlPath;
            //this is a url that looks like an article url.  We want to modify it and create the new one.
            Hashtable friendlyUrlIndex = null; //the friendly url index is the lookup we use
            foreach (Match categoryUrlMatch in categoryUrlMatches)
            {
                string rawId = categoryUrlMatch.Groups["categoryId"].Value;
                int categoryId = 0;
                if (int.TryParse(rawId, out categoryId))
                {
                    
                    //we have obtained the item Id out of the Url
                    //get the friendlyUrlIndex (it comes from the database via the cache)
                    if (friendlyUrlIndex ==null)
                        friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(tab.PortalID, provider, options, clOptions);

                    if (friendlyUrlIndex != null)
                    {
                        string furlkey = null;
                        //item urls are indexed with p + categoryID ("c5") 
                        furlkey = "c" + categoryId.ToString() + "-" + cultureCode;  //create the lookup key for the friendly url index

                        string path = (string)friendlyUrlIndex[furlkey];//check if in the index
                        if (path == null)
                        {
                            //could be a new item that has been created and isn't in the index
                            //do a direct call and find out if it's there
                            path = UrlController.CheckForMissingItemId(categoryId, "category", tab.PortalID, cultureCode, provider, options, clOptions, friendlyUrlIndex, ref messages);
                        }
                        if (path != null) //got a valid path
                        {
                            //url found in the index for this entry.  So replace the matched part of the path with the friendly url
                            if (categoryUrlMatch.Groups["l"].Success) //if the path had a leading /, then make sure to add that onto the replacement
                                path = provider.EnsureLeadingChar("/", path);

                            /* finish it all off */
                            messages.Add("Item Friendly Url Replacing : " + categoryUrlMatch.Value + " in friendlyUrlPath " + friendlyUrlPath + " with Path : " + path);

                            //this is the point where the Url is modified!
                            //replace the path in the path - which leaves any other parts of a path intact.
                            //categoryUrl = categoryUrlRegex.Replace(friendlyUrlPath, path);//replace the part in the friendly Url path with it's replacement.
                            categoryUrl = categoryUrl.Replace(categoryUrlMatch.Value, path);//replace the part in the friendly url path that was matched with the replacement path

                            //check if this tab is the one specified to not use a path
                            if (provider.NoDnnPagePathTabId == tab.TabID)
                                useDnnPagePath = false;//make this Url relative from the site root

                            //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                            endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;

                            result = true;
                        }
                    }
                }
            }
            return result;

        }
        internal static bool MakeAdvCatUrl(CatalookModuleProvider provider, MatchCollection advCatUrlMatches, Regex advCatUrlRegex, string friendlyUrlPath, TabInfo tab, FriendlyUrlOptions options, CatalookUrlOptions clOptions, string cultureCode, ref string endingPageName, ref bool useDnnPagePath, ref List<string> messages, out string advCatUrl, CategoryTabTable catTabs)
        {
            bool result = false;
            advCatUrl = friendlyUrlPath;
            //this is a url that looks like an article url.  We want to modify it and create the new one.
            Hashtable friendlyUrlIndex = null; //the friendly url index is the lookup we use
            //if it had a 'list' group in it, pull that out for keeping
            string listPathItem = null; string catPageId = null;
            foreach (Match advCatUrlMatch in advCatUrlMatches)
            {
                if (listPathItem == null && advCatUrlMatch.Groups["list"] != null)
                    listPathItem = advCatUrlMatch.Groups["list"].Value;
                if (catPageId == null && advCatUrlMatch.Groups["cpi"] != null)
                    catPageId = advCatUrlMatch.Groups["cpi"].Value;

                string rawId = advCatUrlMatch.Groups["advCatId"].Value;
                int advCatId = 0;
                if (int.TryParse(rawId, out advCatId))
                {
                    //we have obtained the item Id out of the Url
                    //get the friendlyUrlIndex (it comes from the database via the cache)
                    if (friendlyUrlIndex == null)
                        friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(tab.PortalID, provider, options, clOptions);

                    if (friendlyUrlIndex != null)
                    {
                        string furlkey = null;
                        //item urls are indexed with ac + advCatID ("ac5") 
                        furlkey = "ac" + advCatId.ToString() + "-" + cultureCode;  //create the lookup key for the friendly url index

                        string path = (string)friendlyUrlIndex[furlkey];//check if in the index
                        if (path == null)
                        {
                            
                            path = UrlController.CheckForMissingItemId(advCatId, "advCategory", tab.PortalID, cultureCode, provider, options, clOptions, friendlyUrlIndex, ref messages);
                            
                        }
                        if (path != null) //got a valid path
                        {
                            if (catTabs != null && catTabs.ContainsKey(advCatId) && (tab.TabID == (int)catTabs[advCatId]))
                            {
                                //url found in the index for this entry, but it is also in the tab/category table, which means we are going to hide
                                //the category url path, and rely on this provider to re-insert it whenever the tab is requested.
                                
                                messages.Add("Item Friendly Url Replacing : " + advCatUrlMatch.Value + " in friendlyUrlPath " + friendlyUrlPath + " with no value, cat is associated with tab : " + tab.TabID.ToString());

                                advCatUrl = advCatUrl.Replace(advCatUrlMatch.Value, "");//replace the part in the friendly url path that was matched with an empty string
                                //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                                endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;
                            }
                            else
                            {
                                //url found in the index for this entry.  So replace the matched part of the path with the friendly url
                                if (advCatUrlMatch.Groups["l"].Success) //if the path had a leading /, then make sure to add that onto the replacement
                                    path = provider.EnsureLeadingChar("/", path);

                                /* finish it all off */
                                messages.Add("Item Friendly Url Replacing : " + advCatUrlMatch.Value + " in friendlyUrlPath " + friendlyUrlPath + " with Path : " + path);

                                //this is the point where the Url is modified!
                                //replace the path in the path - which leaves any other parts of a path intact.
                                //advCatUrl = advCatUrlRegex.Replace(friendlyUrlPath, path);//replace the part in the friendly Url path with it's replacement.
                                advCatUrl = advCatUrl.Replace(advCatUrlMatch.Value, path);//replace the part in the friendly url path that was matched with the replacement path

                                //check if this tab is the one specified to not use a path
                                if (provider.NoDnnPagePathTabId == tab.TabID)
                                    useDnnPagePath = false;//make this Url relative from the site root

                                //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                                endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;
                            }
                            result = true;
                        }
                    }
                }
            }
            if (advCatUrl != null && listPathItem != null)
                //add the list path back on again
                advCatUrl += listPathItem;
            if (advCatUrl != null && catPageId != null)
                advCatUrl += catPageId;
            return result;
        }
        /// <summary>
        /// Combines two querystrings and returns a single value.  Ensures there are no duplicate keys
        /// </summary>
        /// <param name="existingQs"></param>
        /// <param name="newQs"></param>
        /// <param name="existingQsTakesPrecedence"></param>
        /// <returns></returns>
        internal static string CombineQueryStrings(string existingQs, string newQs, bool existingQsTakesPrecedence)
        {
            string result = existingQs;
            if (string.IsNullOrEmpty(newQs) == false)
            {
                //split and combine the two querystrings, ensuring there are no duplicates.  
                Regex qsSplitter = new Regex(@"(?<q>^\?|&)(?<pair>(?<key>[^=]*)=(?<val>[^&]*))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
                MatchCollection existingMatches = qsSplitter.Matches(existingQs);
                MatchCollection newMatches = qsSplitter.Matches(newQs);
                StringBuilder output = new StringBuilder();
                string qsFormat = "{0}{1}={2}";
                List<string> dupKeys = new List<string>();
                foreach (Match pairMatch in existingMatches)
                {
                    string key = pairMatch.Groups["key"].Value;
                    string val = pairMatch.Groups["val"].Value;
                    string q = pairMatch.Groups["q"].Value;
                    bool dupFound = false;
                    //output unless a conflict is found with the newQs
                    foreach (Match newPairMatch in newMatches)
                    {
                        string newKey = newPairMatch.Groups["key"].Value;
                        if (string.Compare(key, newKey, true) == 0)
                        {
                            //this is a duplicate
                            dupFound = true;
                            if (existingQsTakesPrecedence == false)
                                val = newPairMatch.Groups["val"].Value;
                            output.AppendFormat(qsFormat, q, key, val);
                            dupKeys.Add(key.ToLower());
                            break;
                        }
                    }
                    //if no dup found, then output first part
                    if (dupFound == false)
                        output.AppendFormat(qsFormat, q, key, val);
                }
                //now go through the new qs and add all the non-dup keys
                string newQ = "&";
                if (output.Length == 0)
                    newQ = "?";
                foreach (Match newPairMatch in newMatches)
                {
                    string newKey = newPairMatch.Groups["key"].Value;
                    if (dupKeys.Contains(newKey.ToLower()) == false)
                    {
                        string newVal = newPairMatch.Groups["val"].Value;
                        output.AppendFormat(qsFormat, newQ, newKey, newVal);
                        newQ = "&";
                    }
                }
                //return the new output 
                if (output.Length > 0)
                    result = output.ToString();
            }
            return result;
        }

        #endregion
        #region debug data check
        internal static bool CheckForDebug(HttpRequest request, NameValueCollection queryStringCol, bool checkRequestParams)
        {
            string debugValue = ""; bool retVal = false;
            string debugToken = "_cpdebug";
            if (queryStringCol != null && queryStringCol[debugToken] != null)
            {
                debugValue = queryStringCol[debugToken];
            }
            else
                if (checkRequestParams)
                {
                    //798 : change reference to debug parameters
                    if (request != null && request.Params != null)
                        debugValue = (request.Params.Get("HTTP_" + debugToken.ToUpper()));
                    if (debugValue == null) debugValue = "false";
                }
            switch (debugValue.ToLower())
            {
                case "true":
                    retVal = true;
                    break;
            }
            return retVal;
        }

        #endregion





    }
}
